#include "Engine.h"

#include <iostream> // for std::cout
#include <map> // for keymap

/*
================================

================================
*/
Engine::Engine() :
	frames_elapsed(0), quit( false ),
	screen(0),
	root(0),
		plugins_cfg( Ogre::StringUtil::BLANK ),
		resources_cfg( Ogre::StringUtil::BLANK )
{
	
}

/*
================================

================================
*/
Engine::~Engine()
{
	
}

/*
================================
GameEngine::setVideoMode

TODO: On Windows, OpenGL context destroyed; reload all textures
================================
*/
static const unsigned int SCREEN_BPP = 32;
bool Engine::setVideoMode( const int wx, const int wy, const bool fullscreen )
{
	// Caption before video mode
	SDL_WM_SetCaption( "Loading...", NULL );

	// Specify OpenGL double buffer before video mode
	if ( SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 ) == -1 ) {
		std::cerr << "Video initialization failed: "
			<< SDL_GetError() << std::endl;
		return false;
	}

	// OpenGL context
	Uint32 flags = SDL_OPENGL;
//	if ( fullscreen ) flags |= SDL_FULLSCREEN;
//	static const int SCREEN_BPP = 32;
	screen = SDL_SetVideoMode( wx, wy, SCREEN_BPP, flags );
	if ( screen == NULL ) {
		std::cerr << "Video initialization failed: "
			<< SDL_GetError() << std::endl;
		return false;
	}

	return true;
}

/*
================================

================================
*/
std::map < SDLKey, Button > keymap1, keymap2;
bool Engine::init(bool initSDL, NetworkHandler *serverHandler, NetworkHandler *clientHandler, bool amIAlsoServer)
{
	server = serverHandler;
	client = clientHandler;
	isServer = amIAlsoServer;
	
	srand( time(NULL));
	/* Engine */
	frames_elapsed = 0;
	quit = false;

	keymap1[ SDLK_SPACE ]	= IS_START;
	keymap1[ SDLK_UP ]		= IS_U;
	keymap1[ SDLK_DOWN ]	= IS_D;
	keymap1[ SDLK_LEFT ]	= IS_L;
	keymap1[ SDLK_RIGHT ]	= IS_R;
	keymap1[ SDLK_z ]		= IS_A;
	keymap1[ SDLK_x ]		= IS_B;
	keymap1[ SDLK_c ]		= IS_X;
	keymap1[ SDLK_v ]		= IS_Y;
	keymap1[ SDLK_LSHIFT ]	= IS_SL;
	keymap1[ SDLK_RSHIFT ]	= IS_SR;

	keymap2[ SDLK_RETURN ]	= IS_START;
	keymap2[ SDLK_w ]		= IS_U;
	keymap2[ SDLK_s ]		= IS_D;
	keymap2[ SDLK_a ]		= IS_L;
	keymap2[ SDLK_d ]		= IS_R;
	keymap2[ SDLK_g ]		= IS_A;
	keymap2[ SDLK_h ]		= IS_B;
	keymap2[ SDLK_j ]		= IS_X;
	keymap2[ SDLK_k ]		= IS_Y;
	keymap2[ SDLK_q ]		= IS_SL;
	keymap2[ SDLK_e ]		= IS_SR;

	// TODO: where does this go?
	unsigned int wx = 1200;
	unsigned int wy = 800;
	bool fullscreen = false;

	/* SDL */
	if(initSDL){
		if ( SDL_Init( SDL_INIT_EVERYTHING ) == -1 ) {
			std::cerr << "SDL initialization failed: "
				<< SDL_GetError() << std::endl;
			return false;
		}
	}
	int audio_rate = 44100;
  	Uint16 audio_format = AUDIO_S16SYS; /* 16-bit stereo */
 	int audio_channels = 2;
  	int audio_buffers = 4096;
	if(Mix_OpenAudio(audio_rate, audio_format, audio_channels, audio_buffers)) {
    		std::cerr << "Unable to open audio!\n";
   		exit(1);
  	}
	if(initSDL){
		if(SDLNet_Init() == -1){
			std::cerr << "Unable to initialize networking.";
			exit(1);
		}
	}
	if ( !setVideoMode( wx, wy, false ) ) {
		return false;
	}

	/* OGRE */

	// Strings
#ifdef _DEBUG
	plugins_cfg = "plugins_d.cfg";
	resources_cfg = "resources_d.cfg";
#else
	plugins_cfg = "plugins.cfg";
	resources_cfg = "resources.cfg";
#endif

	// Root
	root = new Ogre::Root( plugins_cfg );

	// RenderSystem
	// TODO: This is more complicated on Windows.
	Ogre::RenderSystem* rs = root->getRenderSystemByName( "OpenGL Rendering Subsystem" );
	root->setRenderSystem( rs );
	rs->setConfigOption( "Full Screen", "No" );
	rs->setConfigOption( "Video Mode", "1200 x 800 @ 32-bit colour" );

	// RenderWindow
	Ogre::NameValuePairList misc;
	misc[ "vsync" ] = Ogre::String( "No" );
	misc[ "currentGLContext" ] = Ogre::String( "True" );

	root->initialise( false );
	window = root->createRenderWindow( "RenderWindow", wx, wy, fullscreen, &misc );
	window->setVisible( true );

	// Resources
	Ogre::ConfigFile cf;
	cf.load( resources_cfg );

	// Go through all sections & settings in the file
	Ogre::ConfigFile::SectionIterator seci = cf.getSectionIterator();

	Ogre::String secName, typeName, archName;
	while ( seci.hasMoreElements() ) {
		secName = seci.peekNextKey();
		Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext();
		Ogre::ConfigFile::SettingsMultiMap::iterator i;
		for ( i = settings->begin(); i != settings->end(); ++i ) {
			typeName = i->first;
			archName = i->second;
			Ogre::ResourceGroupManager::getSingleton().addResourceLocation(
				archName, typeName, secName );
		}
	}

	// Initialize resources after Root, RenderSystem, RenderWindow
	Ogre::TextureManager::getSingleton().setDefaultNumMipmaps( 5 );
	Ogre::ResourceGroupManager::getSingleton().initialiseAllResourceGroups();

	/* Done */
	return true;
}

/*
================================
GameEngine::cleanup

Cleans up the state stack.
Shuts down SDL.
TODO: Shuts down OGRE.
================================
*/
void Engine::cleanup()
{
	// Engine
	// cleanup all the states
	while ( !states.empty() ) {
		states.back()->cleanup( this );
		states.pop_back();
	}

	// OGRE
	delete root;
	root = 0;
	Mix_CloseAudio();
	// SDL
	SDLNet_Quit();
	SDL_Quit();
}

/*
================================
time_left

Support function for GameEngine::run.
http://www.libsdl.org/intro.en/usingtimers.html
================================
*/
Uint32 next_time;
Uint32 time_left()
{
	Uint32 now = SDL_GetTicks();
	if ( next_time <= now ) {
		// If we're going to return 0, we should also reset next_time.
		// This fixes the problem of next_time taking several frames to
		// catch up (causing a big speed-up) when the window is dragged
		// (since dragging the window simulates a huge processing lag).
		next_time = now;
		return 0;
	}
	else {
		return next_time - now;
	}
}

/*
================================
Engine::run

Main loop.
================================
*/
static const unsigned int TICK_INTERVAL = 17; // milliseconds
void Engine::run()
{
	next_time = SDL_GetTicks() + TICK_INTERVAL;

	while ( !states.empty() && !quit )
	{
		// Poll for input even when unfocused
		pollInput();

		// if ( SDL_GetAppState() & SDL_APPINPUTFOCUS ) {
		if ( true ) {
			input();
			update();
			draw();

			setCaption();
		}

		SDL_Delay( time_left() );
		next_time += TICK_INTERVAL;
	}
}

/*
================================
Engine::pop

Cleans up and removes the current state.
================================
*/
void Engine::pop()
{
	if ( !states.empty() ) {
		states.back()->cleanup( this );
		states.pop_back();
	}
}

/*
================================
Engine::push
================================
*/
void Engine::push( State* s )
{
	states.push_back( s );
	states.back()->init( this );
}

/*
================================
Engine::change

Pops the current state and pushes the new state.

A GameState already running can effect a reset
by passing itself to this function:
game->change( this );
================================
*/
void Engine::change( State* s )
{
	pop();
	push( s );
}

/*
================================

================================
*/
void Engine::pollInput()
{
	is1.clock();
	is2.clock();

	SDL_Event event;
	while ( SDL_PollEvent( &event ) ) {
	switch ( event.type )
	{
	case SDL_KEYDOWN: {
		SDLKey sym = event.key.keysym.sym;

		// Put input into InputSets
		std::map < SDLKey, Button >::iterator kmi;

		kmi = keymap1.find( sym );
		if ( kmi != keymap1.end() ) {
			is1.set( kmi->second, true );
		}

		kmi = keymap2.find( sym );
		if ( kmi != keymap2.end() ) {
			is2.set( kmi->second, true );
		}

		// State stack control, for testing.
		if ( sym == SDLK_HOME ) {
			Engine::change( states.back() );
		}
		if ( sym == SDLK_END || sym == SDLK_ESCAPE ) {
			Engine::pop();
		}
	}
		break;

	case SDL_KEYUP: {
		SDLKey sym = event.key.keysym.sym;

		// Put input into InputSets
		std::map < SDLKey, Button >::iterator kmi; // Key map iterator

		kmi = keymap1.find( sym );
		if ( kmi != keymap1.end() ) {
			is1.set( kmi->second, false );
		}

		kmi = keymap2.find( sym );
		if ( kmi != keymap2.end() ) {
			is2.set( kmi->second, false );
		}
	}
		break;

	case SDL_QUIT:
		quit = true;
		break;
	}
	}
}

/*
================================
Engine::input
================================
*/
void Engine::input()
{
	if ( !states.empty() ) {
		states.back()->input( this );
	}
}

/*
================================
Engine::update
================================
*/
void Engine::update()
{
	if ( !states.empty() ) {
		states.back()->update( this );
	}

	frames_elapsed++;
}

/*
================================
Engine::draw
================================
*/
void Engine::draw()
{
	
	root->renderOneFrame();
	
	if ( !states.empty() ) {
		states.back()->draw( this );
	}
	
	SDL_GL_SwapBuffers();
}

/*
================================
Engine::setCaption

Displays runtime on the title bar.
================================
*/
static const unsigned int FPS = 60;
void Engine::setCaption()
{
	std::ostringstream caption;
	caption << "Engine";

	// Display frame count
	int x = frames_elapsed;
	int f = x % FPS; x /= FPS;
	int s = x % 60; x /= 60;
	int m = x % 60; x /= 60;
	int h = x;
	caption << " || Time: ";
	if ( h ) caption << h << "h ";
	if ( m ) caption << m << "m ";
	if ( s ) caption << s << "s ";
	if ( f < 10 ) caption << "0"; // zero in front of framecount
	caption << f << "f"; // always show framecount

	if ( !states.empty() ) {
		states.back()->setCaption( caption );
	}

	// Send caption to window title bar
	SDL_WM_SetCaption( caption.str().c_str(), 0 );
}

